package controller;

import components.Util;
import components.exceptions.BinaryToIntException;
import components.exceptions.IntToBinaryException;
import controller.RegisterController;
import controller.notify.Notify;

/**
 * Memory controller control the behavior of the memory, including the reaction
 * to each notify.
 * 
 * @author Chz
 * 
 */
public class MemoryController extends Controller {

	private static MemoryController self;
	private Notify notify = null;

	private MemoryController() {
	}

	public static MemoryController getInstance() {
		if (self == null)
			self = new MemoryController();

		return self;
	}

	public synchronized void notify(Notify n) {
		this.notify = n;
	}

	/**
	 * 
	 * 
	 * @see controller.ControllerListener#notify(controller.notify.Notify)
	 */
	@Override
	public void run() {
		switch (notify.getType()) {
		case Notify.FETCH:
			fetchMARFromPC();
			break;
		case Notify.READ:
			readFromMem();
			break;
		case Notify.WRITE:
			writeIntoMem();
			break;
		case Notify.DONE:
			System.err.println("Notify:DONE received in MemoryController!");
		default:
			System.err
					.println("fatal error in MemoryController, undefined Notify type "
							+ "received!!!!!!!!!!!!!!!!");
		}
	}

	/**
	 * under memory controller, doing "notify == fetch" branch.
	 */
	private void fetchMARFromPC() {

		// MAR = PC
		regs.setMAR(regs.getPC());
		// MBR = MEM(MAR)
		try {
			regs.setMBR(mem.getContent(regs.getMAR()));

		} catch (NullPointerException e) {
			System.err.println("halt");
			bus.setSingleStep(true);

		}
		// send notify:done to RegisterController
		bus.sendNotify(new Notify(Notify.DONE),
				RegisterController.getInstance());
	}

	/**
	 * under memory controller "notify == read" branch.
	 */
	private void readFromMem() {
		// do indexing and addressing first, make sure the MAR is the wanted
		// address
		IndexingAndAddressing();

		// MBR = MEM(MAR)
		regs.setMBR(mem.getContent(regs.getMAR()));

		// send notify:done to ExecutionController
		bus.sendNotify(new Notify(Notify.DONE),
				ExecutionController.getInstance());
	}

	/**
	 * under memory controller "notify == write" branch.
	 */
	private void writeIntoMem() {
		// Since writing always the last step of a circle, at this time, the
		// value of MAR is always the correct value.
		IndexingAndAddressing();
		// MEM(MAR) = MBR
		mem.setContent(regs.getMAR(), regs.getMBR());

		// when finish this, send nofity:done to pc controller to finish the
		// execution of this instruction
		bus.sendNotify(new Notify(Notify.DONE), PCController.getInstance());
	}

	/**
	 * check the X0 and IX registers, doing indexing and addressing, put the
	 * final address into MAR.
	 */
	private void IndexingAndAddressing() {
		if (regs.getI() == 0) { // no indirect addressing
			if (regs.getIX() == 0) { // no indexing
				// MAR = C(ADDR)
				regs.setMAR((regs.getADDR()));
			} else { // indexing
				// MAR = C(X0) + C(ADDR)
				try {
					int cAddrInt = Util.binaryToInt(regs.getADDR());
					int cX0Int = Util.binaryToInt(regs.getX0());
					int mar = cX0Int + cAddrInt;
					regs.setMAR(Util.intToBinary(mar, regs.getMAR().length));
				} catch (BinaryToIntException e) {
					System.err.println(e.getLocalizedMessage());
					e.printStackTrace();
				} catch (IntToBinaryException e) {
					System.err.println(e.getLocalizedMessage());
					e.printStackTrace();
				}
			}
		} else { // I == 1, indirect addressing
			if (regs.getIX() == 0) { // no indexing
				// MAR = C(ADDR)
				regs.setMAR(regs.getADDR());
			} else { // indexing
				// MAR = C(X0) + C(ADDR)
				try {
					int cAddrInt = Util.binaryToInt(regs.getADDR());
					int cX0Int = Util.binaryToInt(regs.getX0());
					int mar = cX0Int + cAddrInt;
					regs.setMAR(Util.intToBinary(mar, regs.getMAR().length));
				} catch (BinaryToIntException e) {
					System.err.println(e.getLocalizedMessage());
					e.printStackTrace();
				} catch (IntToBinaryException e) {
					System.err.println(e.getLocalizedMessage());
					e.printStackTrace();
				}
				// MBR = MEM(MAR)
				regs.setMBR(mem.getContent(regs.getMAR()));
				// MAR = C(MBR)
				regs.setMAR(regs.getMBR());
			}
		}
	}
}
